package algebra.query.od;

import java.util.HashMap;
import java.util.List;
import java.util.Map;

import kb.DescriptionLogicsKB;
import reasoner.KBReason;

import utils.exception.CoreReasoningException;
import utils.exception.OrderingDescriptionException;
import utils.exception.SyntaxParsingException;
import utils.parser.Creator;
import entities.assertion.ConceptAssertion;
import entities.concept.Concept;
import entities.concept.DomainConcept;
import entities.role.Role;

public abstract class OrderDep {
	public final boolean compareOptimization = true;
	protected static Map<Concept, Concept> subsumptionCache = new HashMap<Concept, Concept>();

	public abstract String toString();

	public OrderDep() {

	}


	// get an order dep from a string using the parsers
	public static OrderDep getOrderDep(String ods) {
		try {
			return Creator.getOdFromString(ods);
		} catch (SyntaxParsingException e) {
			e.printStackTrace();
		}
		return null;
	}

	public OrderDepFeature asOdFeature() {
		return (OrderDepFeature) this;
	}

	public OrderDepNoop asOdNoop() {
		return (OrderDepNoop) this;
	}

	public OrderDepOid asOdOid() {
		return (OrderDepOid) this;
	}

	public OrderDepPart asOdPart() {
		return (OrderDepPart) this;
	}

//	public OrderDepSub asOdSubsume() {
//		return (OrderDepSub) this;
//	}

	/*
	 * A utility function used by web services to generate an Od from a list of
	 * features (ending with noop)
	 */
	public static OrderDep getOdFeatureFromArray(List<String> fs)
			throws OrderingDescriptionException {
		if (fs == null || fs.size() < 1) {
			return new OrderDepNoop();
		}
		OrderDep sub = new OrderDepNoop();
		while (!fs.isEmpty()) {
			String s = fs.remove(fs.size() - 1);
			Role f = Role.createRoleAsIs(s);
			sub = new OrderDepFeature(f, sub);
		}
		return sub;
	}

	/*
	 * @return -1 if ca1 orders before ca2 1 if ca1 orders after ca2 0 if the
	 * two are incomparable or equal
	 */
	public int compare(ConceptAssertion ca1, ConceptAssertion ca2)
			throws OrderingDescriptionException, CoreReasoningException {
		if (this instanceof OrderDepNoop) {
			return 0;
		} else if (this instanceof OrderDepFeature) {
			return this.asOdFeature().compareFeature(ca1, ca2);
		} else if (this instanceof OrderDepOid) {
			int comp = ca1.getIndividual().getName()
					.compareTo(ca2.getIndividual().getName());
			return comp > 0 ? 1 : (comp == 0 ? 0 : -1);
		} else if (this instanceof OrderDepPart) {
			return this.asOdPart().comparePartition(ca1, ca2);
		} 
//		else if (this instanceof OrderDepSub) {
//			return this.asOdSubsume().compareOdSubsume(ca1, ca2);
//		}
		return 0;
	}

	protected boolean OdSubsumes(Concept a, Concept b, DescriptionLogicsKB kb) {
		KBReason res = new KBReason(false);
		if (!compareOptimization) {
			try {
				return res.subsumes(a, b, kb);
			} catch (CoreReasoningException e) {
				e.printStackTrace();
			}
		}
		if (subsumptionCache.get(a) == b) {
			return true;
		}
		try {
			if (res.subsumes(a, b, kb)) {
				subsumptionCache.put(a, b);
				return true;
			}
		} catch (CoreReasoningException e) {
			e.printStackTrace();
		}
		return false;
	}

	private static int cacheHits = 0;

	public static void setCacheHits(int cacheHits) {
		OrderDep.cacheHits = cacheHits;
	}

	public static int getCacheHits() {
		return cacheHits;
	}

	private static int structuralSubsumptions = 0;

	public static void setStructuralSubsumptions(int structuralSubsumptions) {
		OrderDep.structuralSubsumptions = structuralSubsumptions;
	}

	public static int getStructuralSubsumptions() {
		return structuralSubsumptions;
	}

	/*
	 * Test if the given concept is sorted by some feature
	 */
	public boolean majorSortOnArg(Concept c) {
		if (c.isTop()) {
			return true;
		}
		if (this instanceof OrderDepFeature) {
			if (c instanceof DomainConcept) {
				// only for concepts (f op k)
				Role f = (Role) ((DomainConcept<?, ?>) c).getRealFeature();
				String s = (String) ((DomainConcept<?, ?>) c).getRealValue();
				if (f != null && s != null
						& f.equals(this.asOdFeature().getOdFeature())) {
					return true;
				}
			}
		}
		return false;
	}
	
	//if od2 is the last sort arg of od1
	public static boolean lastSortBy(OrderDep od1, OrderDep od2){
		if(od1.equals(od2)){
			return true;
		}
		if(od1 instanceof OrderDepFeature){
			return lastSortBy(od1.asOdFeature().getSubOd(), od2);
		}else if(od1 instanceof OrderDepNoop){
			return od2 instanceof OrderDepNoop;
		}else if(od1 instanceof OrderDepOid){
			return od2 instanceof OrderDepOid;
		}else if(od1 instanceof OrderDepPart){
			//partition order: which is the last sort?
			return false;
		}
		return false;
	}
	
	//replace last order arg in Od1 by Od2
	public static OrderDep replaceLastSortBy(OrderDep od1, OrderDep od2){
		if(lastSortBy(od1, od2)){
			//no need to replace
			return od1;
		}
		if(od1 instanceof OrderDepNoop || od1 instanceof OrderDepOid){
			//base case, just replace 
			return od2;
		}else if(od1 instanceof OrderDepFeature){
			OrderDep sub = replaceLastSortBy(od1.asOdFeature().getSubOd(), od2);
			return new OrderDepFeature(od1.asOdFeature().getOdFeature(), sub);
		}else if(od1 instanceof OrderDepPart){
			//fail to replace due to partition
			return od1;
		}
		return od1;
	}

	public abstract String toSerializedString();

}
